using System;
using System.Collections.Generic;
using System.Numerics;
using TTEngine.Graphics;
using TTEngine.TTMath;
using TTEngine.Resource;
using voidbox;


namespace TTEngine.Framework.UI
{
    public enum UIDragDirection
    {
        None,
        LeftToRight,
        UpToDown,
        Both
    }

    public enum UIDirection
    {
        LeftToRight,
        UpToDown,
        RightToLeft,
        DownToUp
    }

    public enum UIDirection2
    {
        Horizontal,
        Vertical
    }

    public enum UIHAlign
    {
        Left,
        Middle,
        Right
    }

    public enum UIVAlign
    {
        Top,
        Middle,
        Bottom
    }

    public class UIRect
    {
        public float RadioX1 { get; set; }
        public float RadioY1 { get; set; }
        public float RadioX2 { get; set; }
        public float RadioY2 { get; set; }
        public float OffsetX1 { get; set; }
        public float OffsetY1 { get; set; }
        public float OffsetX2 { get; set; }
        public float OffsetY2 { get; set; }
        public bool SizeOnly { get; set; }

        public UIRect Clone()
        {
            return new UIRect
            {
                RadioX1 = this.RadioX1,
                RadioX2 = this.RadioX2,
                RadioY1 = this.RadioY1,
                RadioY2 = this.RadioY2,
                OffsetX1 = this.OffsetX1,
                OffsetX2 = this.OffsetX2,
                OffsetY1 = this.OffsetY1,
                OffsetY2 = this.OffsetY2,
                SizeOnly = this.SizeOnly
            };
        }

        public float GetWidth()
        {
            if (!SizeOnly)
                throw new InvalidOperationException("only sizeonly can getwidth");
            return OffsetX2 - OffsetX1;
        }

        public float GetHeight()
        {
            if (!SizeOnly)
                throw new InvalidOperationException("only sizeonly can getheight");
            return OffsetY2 - OffsetY1;
        }

        public void SetPos(float x, float y)
        {
            if (!SizeOnly)
                throw new InvalidOperationException("only sizeonly can setpos");
            float w = GetWidth();
            float h = GetHeight();
            OffsetX1 = x;
            OffsetY1 = y;
            OffsetX2 = x + w;
            OffsetY2 = y + h;
        }
        public fAABB GetFinalRect(fAABB parentFinal)
        {
            float px1 = parentFinal.x1;
            float px2 = parentFinal.x2;
            float py1 = parentFinal.y1;
            float py2 = parentFinal.y2;
            float x1 = px1 + RadioX1 * (px2 - px1) + OffsetX1;
            float y1 = py1 + RadioY1 * (py2 - py1) + OffsetY1;
            float x2 = px1 + RadioX2 * (px2 - px1) + OffsetX2;
            float y2 = py1 + RadioY2 * (py2 - py1) + OffsetY2;
            return new fAABB(x1, y1, x2, y2);
        }

        public fAABB GetFinalRectScale(fAABB parentFinal, float scale)
        {
            float px1 = parentFinal.x1;
            float px2 = parentFinal.x2;
            float py1 = parentFinal.y1;
            float py2 = parentFinal.y2;
            float x1 = (px1 + RadioX1 * (px2 - px1) + OffsetX1) * scale;
            float y1 = (py1 + RadioY1 * (py2 - py1) + OffsetY1) * scale;
            float x2 = (px1 + RadioX2 * (px2 - px1) + OffsetX2) * scale;
            float y2 = (py1 + RadioY2 * (py2 - py1) + OffsetY2) * scale;
            return new fAABB(x1, y1, x2, y2);
        }

        public void SetAsFill()
        {
            RadioX1 = 0;
            RadioY1 = 0;
            RadioX2 = 1;
            RadioY2 = 1;
            OffsetX1 = 0;
            OffsetX2 = 0;
            OffsetY1 = 0;
            OffsetY2 = 0;
            SizeOnly = false;
        }

        public void SetBySize(float width, float height)
        {
            RadioX1 = 0;
            RadioY1 = 0;
            RadioX2 = 0;
            RadioY2 = 0;
            OffsetX1 = 0;
            OffsetX2 = width;
            OffsetY1 = 0;
            OffsetY2 = height;
            SizeOnly = true;
        }

        public void SetByPosAndSize(float x, float y, float width, float height)
        {
            RadioX1 = 0;
            RadioY1 = 0;
            RadioX2 = 0;
            RadioY2 = 0;
            OffsetX1 = x;
            OffsetX2 = x + width;
            OffsetY1 = y;
            OffsetY2 = y + height;
            SizeOnly = true;
        }

        public void SetByRect(Rectangle rect)
        {
            RadioX1 = 0;
            RadioY1 = 0;
            RadioX2 = 0;
            RadioY2 = 0;
            OffsetX1 = rect.X;
            OffsetX2 = rect.X + rect.Width;
            OffsetY1 = rect.Y;
            OffsetY2 = rect.Y + rect.Height;
            SizeOnly = true;
        }

        public void SetHPosByLeftBorder(float width, float border = 0)
        {
            RadioX1 = 0;
            RadioX2 = 0;
            OffsetX1 = border;
            OffsetX2 = border + width;
        }

        public void SetHPosByCenter(float width)
        {
            RadioX1 = 0.5f;
            RadioX2 = 0.5f;
            OffsetX1 = width * -0.5f;
            OffsetX2 = width * 0.5f;
            SizeOnly = false;
        }

        public void SetHPosByRightBorder(float width, float border = 0)
        {
            RadioX1 = 1;
            RadioX2 = 1;
            OffsetX1 = -border - width;
            OffsetX2 = -border;
            SizeOnly = false;
        }

        public void SetHPosFill(float borderLeft = 0, float borderRight = 0)
        {
            RadioX1 = 0;
            RadioX2 = 1;
            OffsetX1 = borderLeft;
            OffsetX2 = -borderRight;
            SizeOnly = false;
        }

        public void SetVPosByTopBorder(float height, float border = 0)
        {
            RadioY1 = 0;
            RadioY2 = 0;
            OffsetY1 = border;
            OffsetY2 = border + height;
        }

        public void SetVPosByCenter(float height)
        {
            RadioY1 = 0.5f;
            RadioY2 = 0.5f;
            OffsetY1 = height * -0.5f;
            OffsetY2 = height * 0.5f;
            SizeOnly = false;
        }

        public void SetVPosByBottomBorder(float height, float border = 0)
        {
            RadioY1 = 1;
            RadioY2 = 1;
            OffsetY1 = -border - height;
            OffsetY2 = -border;
            SizeOnly = false;
        }

        public void SetVPosFill(float borderTop = 0, float borderBottom = 0)
        {
            RadioY1 = 0;
            RadioY2 = 1;
            OffsetY1 = borderTop;
            OffsetY2 = -borderBottom;
            SizeOnly = false;
        }
    }


    public enum ChildChangeState
    {
        NoChange,
        AddOne,
        Dirty
    }

    public abstract class UIBaseElement
    {
        private static int _id = 0;
        private int _thisId;

        protected UIBaseElement()
        {
            _id++;
            _thisId = _id;
        }

        public int GetID() => _thisId;

        public object Tag { get; set; }
        public UIRect LocalRect { get; set; } = new UIRect();
        internal UIBaseElement _parent;
        public UIBaseElement GetParent() => _parent;

        public UICanvas GetCanvas()
        {
            if (_parent == null)
            {
                if(this is UICanvas)
                     return this as UICanvas;
                return null;
            }
            if (_parent is UICanvas)
            {
                return _parent as UICanvas;
            }
            return _parent.GetCanvas();
        }

        public virtual bool IsContainer() => false;
        public bool Enable { get; set; } = true;
        protected Color _colorFinal = Color.White;

        public virtual void CancelTouch() { }

        public virtual bool OnTouch(UICanvas canvas, int touchId, bool press, bool move, float x, float y) => false;

        public virtual bool OnWheel(UICanvas canvas, float dx, float dy, float dz) => false;

        public virtual void OnRender(UICanvas canvas) { }

        public virtual void OnUpdate(UICanvas canvas, float delta)
        {
            if (_parent == null)
            {
                _colorFinal = LocalColor;
            }
            else
            {
                _colorFinal = Color.Mul(_parent.GetFinalColor(), LocalColor);
            }
        }

        public virtual void OnFocus() { }
        public virtual void OnUnFocus() { }

        public Color LocalColor { get; set; } = Color.White;
        public Color GetFinalColor() => _colorFinal;

        public fAABB GetWorldRect()
        {
            if (_parent == null)
            {
                float x1 = LocalRect.OffsetX1;
                float y1 = LocalRect.OffsetY1;
                float x2 = LocalRect.OffsetX2;
                float y2 = LocalRect.OffsetY2;
                return new fAABB(x1, y1, x2, y2);
            }
            else
            {
                fAABB pw = _parent.GetWorldRect();
                return LocalRect.GetFinalRect(pw);
            }
        }

        public fAABB GetWorldRectScale(float scale)
        {
            if (_parent == null)
            {
                float x1 = LocalRect.OffsetX1 * scale;
                float y1 = LocalRect.OffsetY1 * scale;
                float x2 = LocalRect.OffsetX2 * scale;
                float y2 = LocalRect.OffsetY2 * scale;
                return new fAABB(x1, y1, x2, y2);
            }
            else
            {
                fAABB pw = _parent.GetWorldRect();
                return LocalRect.GetFinalRectScale(pw, scale);
            }
        }
    }

    public abstract class UIBaseContainer : UIBaseElement
    {
        protected List<UIBaseElement> _children;
        protected ChildChangeState childState = ChildChangeState.NoChange;
        protected UIBaseElement _picked;

        protected UIBaseContainer()
        {
            LocalRect.SetAsFill();
        }

        public int GetChildCount() => _children?.Count ?? 0;

        public void ToTop(UIBaseElement elem)
        {
            RemoveChild(elem);
            AddChild(elem);
        }

        public UIBaseElement GetChild(int index)
        {
            if (_children == null || index >= _children.Count)
                return null;
            return _children[index];
        }

        public int IndexOfChild(UIBaseElement elem)
        {
            if (elem == null)
                return -1;
            return _children?.IndexOf(elem) ?? -1;
        }

        public void InsertChild(UIBaseElement elem)
        {
            if (_children == null)
                _children = new List<UIBaseElement>();
            if (_children.Contains(elem))
                return;

            UIBaseElement p = elem._parent;
            if (p != null)
                (p as UIBaseContainer).RemoveChild(elem);

            _children.Insert(0, elem);

            if (childState == ChildChangeState.NoChange)
            {
                childState = ChildChangeState.AddOne;
            }
            else
            {
                childState = ChildChangeState.Dirty;
            }

            elem._parent = this;
        }

        public void AddChild(UIBaseElement elem)
        {
            if (_children == null)
                _children = new List<UIBaseElement>();
            if (_children.Contains(elem))
                return;

            UIBaseElement p = elem._parent;
            if (p != null)
                (p as UIBaseContainer).RemoveChild(elem);

            _children.Add(elem);

            if (childState == ChildChangeState.NoChange)
            {
                childState = ChildChangeState.AddOne;
            }
            else
            {
                childState = ChildChangeState.Dirty;
            }

            elem._parent = this;
        }

        public void RemoveChild(UIBaseElement elem)
        {
            if (_children == null)
                return;
            int i = _children.IndexOf(elem);
            if (i < 0)
                return;

            _children.RemoveAt(i);
            elem._parent = null;
            childState = ChildChangeState.Dirty;
        }

        public void RemoveChildAt(int index)
        {
            if (_children == null)
                return;
            UIBaseElement elem = _children[index];
            _children.RemoveAt(index);
            elem._parent = null;
            childState = ChildChangeState.Dirty;
        }

        public void RemoveChildAll()
        {
            if (_picked != null)
            {
                UnPick();
            }
            if (_children == null)
                return;
            foreach (var elem in _children)
            {
                elem._parent = null;
            }
            _children.Clear();
            childState = ChildChangeState.Dirty;
        }

        public override void CancelTouch()
        {
            if (_children != null)
            {
                for (int i = _children.Count - 1; i >= 0; i--)
                {
                    UIBaseElement child = _children[i];
                    if (child.Enable)
                    {
                        child.CancelTouch();
                    }
                }
            }
        }

        public override bool OnTouch(UICanvas canvas, int touchId, bool press, bool move, float x, float y)
        {
            if (_children != null)
            {
                for (int i = _children.Count - 1; i >= 0; i--)
                {
                    UIBaseElement child = _children[i];
                    if (child.Enable)
                    {
                        bool kill = child.OnTouch(canvas, touchId, press, move, x, y);
                        if (kill)
                            return true;
                    }
                }
            }
            return false;
        }

        public override bool OnWheel(UICanvas canvas, float dx, float dy, float dz)
        {
            if (_children != null)
            {
                for (int i = _children.Count - 1; i >= 0; i--)
                {
                    UIBaseElement child = _children[i];
                    if (child.Enable)
                    {
                        bool kill = child.OnWheel(canvas, dx, dy, dz);
                        if (kill)
                            return true;
                    }
                }
            }
            return false;
        }

        public override void OnRender(UICanvas canvas)
        {
            if (_children == null)
                return;
            foreach (var child in _children)
            {
                if (child.Enable)
                {
                    child.OnRender(canvas);
                }
            }
        }

        public override void OnUpdate(UICanvas canvas, float delta)
        {
            base.OnUpdate(canvas, delta);
            if (_children == null)
                return;
            foreach (var child in _children)
            {
                if (child.Enable)
                {
                    child.OnUpdate(canvas, delta);
                }
            }
        }

        public UIBaseElement GetPicked() => _picked;

        public Action<UIBaseElement> OnPick { get; set; }

        public void Pick(UIBaseElement pick)
        {
            if (_picked == pick)
                return;
            _picked?.OnUnFocus();
            _picked = pick;
            _picked?.OnFocus();
            OnPick?.Invoke(_picked);
        }

        public void PickAt(int index)
        {
            Pick(_children[index]);
        }

        public void UnPick()
        {
            _picked?.OnUnFocus();
            _picked = null;
        }
    }

    public class UIContainer : UIBaseContainer
    {
    }
}